#include <linux/sched/task_types.h>
#include <linux/percpu.h>
#include <linux/sched/task_stack.h>

#include <riscv/sbi.h>
#include <riscv/smp.h>
#include <riscv/cpu_ops_sbi.h>

struct cpu_operations
{
    int (*cpu_start)(unsigned int cpu,
                     struct task_struct *tidle);
};

/*
 * Ordered booting via HSM brings one cpu at a time. However, cpu hotplug can
 * be invoked from multiple threads in parallel. Define a per cpu data
 * to handle that.
 */
static DEFINE_PER_CPU(struct sbi_hart_boot_data, boot_data);

static int sbi_hsm_hart_start(unsigned long hartid, unsigned long saddr,
                              unsigned long priv)
{
    struct sbiret ret;
    int err = 0;

    ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_START,
                    hartid, saddr, priv, 0, 0, 0);
    if (!ret.error)
        err = sbi_err_map_linux_errno(ret.error);

    return err;
}

static int sbi_cpu_start(unsigned int cpuid, struct task_struct *tidle)
{
    unsigned long boot_addr;
    unsigned long hartid;
    unsigned long hsm_data;
    struct sbi_hart_boot_data *bdata = &per_cpu(boot_data, cpuid);

    boot_addr = &secondary_start_sbi;
    hartid = cpuid_to_hartid_map(cpuid);
    hsm_data = (unsigned long)bdata;

	/* Make sure tidle is updated */
	smp_mb();
	bdata->task_ptr = tidle;
	bdata->stack_ptr = task_pt_regs(tidle);
	/* Make sure boot data is updated */
	smp_mb();

    return sbi_hsm_hart_start(hartid, boot_addr, hsm_data);
}

static const struct cpu_operations cpu_ops_sbi = {
    .cpu_start = sbi_cpu_start,
};

static const struct cpu_operations *cpu_ops = &cpu_ops_sbi;
